/*-
 * Public platform independent Near Field Communication (NFC) library examples
 * 
 * Copyright (C) 2011, Romuald Conty
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  1) Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer. 
 *  2 )Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Note that this license only applies on the examples, NFC library itself is under LGPL
 *
 */

/**
 * @file nfc-read-forum-tag3.c
 * @brief Extract NDEF Message from a NFC Forum Tag Type 3
 * This utility extract (if available) the NDEF Message contained in an NFC Forum Tag Type 3.
 */

/*
 * This implementation was written based on information provided by the
 * following documents:
 *
 * NFC Forum Type 3 Tag Operation Specification
 *  Technical Specification
 *  NFCForum-TS-Type-3-Tag_1.1 - 2011-06-28
 */

#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <getopt.h>
/* #include <sys/param.h> */
/* #include <unistd.h> */

#include <host/nfc.h>
#include <host/nfc-emulation.h>

#include "nfc-utils.h"

static nfc_device_t *pnd;

void print_usage(char *progname)
{
	fprintf(stderr, "usage: %s -o FILE\n", progname);
	fprintf(stderr, "\nOptions:\n");
	fprintf(stderr, "  -o     Extract NDEF message if available in FILE\n");
}

void stop_select(int sig)
{
	(void) sig;
	if (pnd)
		nfc_abort_command(pnd);
	else
		exit(EXIT_FAILURE);
}

void build_felica_frame(const nfc_felica_info_t nfi,
			const byte_t command,
			const byte_t *payload,
			const size_t payload_len,
			byte_t *frame, size_t *frame_len)
{
	frame[0] = 1 + 1 + 8 + payload_len;
	*frame_len = frame[0];
	frame[1] = command;
	memcpy(frame + 2, nfi.abtId, 8);
	memcpy(frame + 10, payload, payload_len);
}

#define CHECK 		0x06
int nfc_forum_tag_type3_check(nfc_device_t *pnd,
			      const nfc_target_t nt,
			      const uint16_t block,
			      const uint8_t block_count,
			      byte_t *data, size_t *data_len)
{
	uint8_t b;
	byte_t payload[1024] = {
		1, /* Services */
		0x0B, 0x00, /* NFC Forum Tag Type 3's Service code */
		block_count,
		0x80, (byte_t)block, /* block 0 */
	};
	size_t payload_len = 1 + 2 + 1;
	byte_t frame[1024];
	size_t frame_len = sizeof(frame);
	byte_t res[1024];
	size_t res_len;
	byte_t status_flag1, status_flag2;
	size_t res_overhead;
	uint8_t felica_res_len;
	
	for (b = 0; b < block_count; b++) {
		if (block < 0x100) {
			payload[payload_len++] = 0x80;
			payload[payload_len++] = block + b;
		} else {
			payload[payload_len++] = 0x00;
			payload[payload_len++] = (block + b) >> 8;
			payload[payload_len++] = (block + b) & 0xff;
		} 
	}
	
	build_felica_frame(nt.nti.nfi, CHECK, payload, payload_len, frame, &frame_len);
	
	if (!nfc_initiator_transceive_bytes(pnd,
					    frame, frame_len,
					    res, &res_len,
					    NULL)) {
		return -1;
	}
	/* 1+1+8+2: LEN + CMD + NFCID2 + STATUS */
	res_overhead = 1 + 1 + 8 + 2;
	if (res_len<res_overhead) { 
		/* Not enough data */
		return -1;
	}
	felica_res_len = res[0];
	if (res_len != felica_res_len) {
		/* Error while receiving felica frame */
		return -1;
	}
	if ((CHECK + 1) != res[1]) {
		/* Command return does not match */
		return -1;
	}
	if (0 != memcmp (&res[2], nt.nti.nfi.abtId, 8)) {
		// NFCID2 does not match
		return -1;
	}
	status_flag1 = res[10];
	status_flag2 = res[11];
	if ((status_flag1) || (status_flag2)) {
		/* Felica card's error */
		fprintf (stderr, "Status bytes: %02x, %02x\n", status_flag1, status_flag2);
		return -1;
	}
	/* const uint8_t res_block_count = res[12]; */
	/* +1 => block count is stored on 1 byte */
	*data_len = res_len - res_overhead + 1;
	memcpy(data, &res[res_overhead+1], *data_len);

	return *data_len;
}

int main(int argc, char *argv[])
{
	int ch;
	char *ndef_output = NULL; 
	size_t n;
	uint16_t b;
	FILE* message_stream = NULL; 
	FILE* ndef_stream = NULL; 
	nfc_modulation_t nm = {
		NMT_FELICA,
		NBR_212,
	};
	nfc_target_t nt;
	int error = EXIT_SUCCESS;
	const byte_t *pbtSensfReq = (byte_t*)"\x00\xff\xff\x01\x00";
	const byte_t abtNfcForumSysCode[] = { 0x12, 0xfc };
	byte_t data[1024];
	size_t data_len = sizeof(data);
	int len;
	int ndef_major_version, ndef_minor_version;
	int available_block_count;
	uint32_t ndef_data_len;
	uint16_t ndef_calculated_checksum = 0;
	uint16_t ndef_checksum;
	uint8_t block_max_per_check;
	uint16_t block_count_to_check;
	
	(void)argc;
	(void)argv;
	
	while ((ch = getopt(argc, argv, "ho:")) != -1) { 
		switch (ch) { 
		case 'h': 
			print_usage(argv[0]); 
			exit (EXIT_SUCCESS); 
			break; 
		case 'o': 
			ndef_output = optarg; 
			break; 
		case '?': 
			if (optopt == 'o') 
				fprintf(stderr, "Option -%c requires an argument.\n", optopt); 
		default: 
			print_usage(argv[0]); 
			exit(EXIT_FAILURE); 
		} 
	} 
	
	if (ndef_output == NULL) { 
		print_usage(argv[0]); 
		exit(EXIT_FAILURE); 
	} 
	
	if ((strlen (ndef_output) == 1) && (ndef_output[0] == '-')) { 
		message_stream = stderr; 
		ndef_stream = stdout; 
	} else { 
		message_stream = stdout; 
		ndef_stream = fopen(ndef_output, "wb"); 
		if (!ndef_stream) { 
			fprintf(stderr, "Could not open file %s.\n", ndef_output); 
			exit(EXIT_FAILURE); 
		} 
	}
	
	pnd = nfc_connect(NULL);
	if (pnd == NULL) {
		ERR("Unable to connect to NFC device");
		exit(EXIT_FAILURE);
	}
	fprintf(message_stream,
		"Connected to NFC device: %s\n", pnd->acName);
	signal(SIGINT, stop_select);
	nfc_initiator_init(pnd);
	fprintf(message_stream,
		"Place your NFC Forum Tag Type 3 in the field...\n");
	
	/* Polling payload (SENSF_REQ) must be present (see NFC Digital
	 * Protol)
	 */
	if (!nfc_initiator_select_passive_target(pnd, nm, pbtSensfReq, 5, &nt)) {
		nfc_perror(pnd, "nfc_initiator_select_passive_target");
		error = EXIT_FAILURE;
		goto error;
	}
	
	/* Check if System Code equals 0x12fc */
	if (0 != memcmp(nt.nti.nfi.abtSysCode,
			abtNfcForumSysCode, 2)) {
		/* Retry with special polling */
		const byte_t *pbtSensfReqNfcForum = (byte_t*)"\x00\x12\xfc\x01\x00";
		if (!nfc_initiator_select_passive_target(pnd, nm, pbtSensfReqNfcForum, 5, &nt)) {
			nfc_perror(pnd, "nfc_initiator_select_passive_target");
			error = EXIT_FAILURE;
			goto error;
		}
		/* Check again if System Code equals 0x12fc */
		if (0 != memcmp(nt.nti.nfi.abtSysCode, abtNfcForumSysCode, 2)) {
			fprintf(stderr, "Tag is not NFC Forum Tag Type 3 compiliant.\n");
			error = EXIT_FAILURE;
			goto error;
		}
	}
	
	/* print_nfc_felica_info(nt.nti.nfi, true); */
	
	if (!nfc_configure(pnd, NDO_EASY_FRAMING, false) ||
	    !nfc_configure(pnd, NDO_INFINITE_SELECT, false)) {
		nfc_perror(pnd, "nfc_configure");
		error = EXIT_FAILURE;
		goto error;
	}
	
	if (0 >= (len = nfc_forum_tag_type3_check(pnd, nt, 0, 1, data, &data_len))) {
		nfc_perror(pnd, "nfc_forum_tag_type3_check");
		error = EXIT_FAILURE;
		goto error;
	}
	
	ndef_major_version = (data[0] & 0xf0) >> 4;
	ndef_minor_version = (data[0] & 0x0f);
	fprintf(message_stream, "NDEF Mapping version: %d.%d\n", ndef_major_version, ndef_minor_version);
	
	available_block_count = (data[3] << 8) + data[4];
	fprintf(message_stream,
		"NFC Forum Tag Type 3 capacity: %d bytes\n",
		available_block_count * 16);
	
	ndef_data_len = (data[11] << 16) + (data[12] << 8) + data[13];
	fprintf(message_stream,
		"NDEF data lenght: %d bytes\n",
		ndef_data_len);
	
	for (n = 0; n < 14; n++)
		ndef_calculated_checksum += data[n];
	
	ndef_checksum = (data[14] << 8) + data[15];
	if (ndef_calculated_checksum != ndef_checksum) {
		fprintf(stderr, "NDEF CRC does not match with calculated one\n");
		error = EXIT_FAILURE;
		goto error;
	}
	if (!ndef_data_len) {
		fprintf(stderr, "Empty NFC Forum Tag Type 3\n");
		error = EXIT_FAILURE;
		goto error;
	}
	
	block_max_per_check = data[1];
	block_count_to_check = (uint16_t)((ndef_data_len / 16) + 1);
	
	data_len = 0;
	for (b = 0;
	     b < (block_count_to_check/block_max_per_check);
	     b += block_max_per_check) {
		size_t len = sizeof(data) - data_len;
		if (!nfc_forum_tag_type3_check(pnd, nt,
					       1+b,
					       MIN(block_max_per_check,
						   (block_count_to_check-(b*block_max_per_check))),
						   data + data_len,
						   &len)) {
			nfc_perror(pnd, "nfc_forum_tag_type3_check");
			error = EXIT_FAILURE;
			goto error;
		}
		data_len += len;
	}
	if (fwrite(data, 1, data_len, ndef_stream) != data_len) {
		fprintf(stderr, "Could not write to file.\n");
		error = EXIT_FAILURE;
		goto error;
	}
	
error:
	fclose(ndef_stream);
	if (pnd) {
		nfc_disconnect(pnd);
	}
	exit(error);
}
